﻿using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Microsoft.Office.Interop.PowerPoint;

namespace Microsoft.Office.PowerPoint.STAMP.Core
{
    /// <summary>
    /// Renders Captions
    /// </summary>
    public class CaptionRenderer
    {
        private readonly StampMediaShape _mediaShape;
        private readonly StampSlide _stampSlide;

        /// <summary>
        /// Initializes a new instance of the <see cref="CaptionRenderer"/> class.
        /// </summary>
        /// <param name="mediaShape">The media shape.</param>
        /// <param name="stampSlide">The stamp slide.</param>
        public CaptionRenderer(StampMediaShape mediaShape, StampSlide stampSlide)
        {
            _mediaShape = mediaShape;
            _stampSlide = stampSlide;
        }

        /// <summary>
        /// Main method for rendering new and updating dirty captions on the slide
        /// </summary>
        /// <param name="captionItemsList">List of new and/or dirty captions</param>
        /// <param name="observer"></param>
        public void RenderCaptions(CaptionItemsList captionItemsList, IProgressObserver observer)
        {
            bool isNullOrEmpty = captionItemsList == null || captionItemsList.CaptionItems == null || captionItemsList.CaptionItems.Count == 0;
            if (isNullOrEmpty)
            {
                _mediaShape.DeleteStoredCaptionsAndIdTags();
                return;
            }

            List<CaptionItem> captions = captionItemsList.CaptionItems;
            Dictionary<string, StampCaptionShape> shapesByInternalId = _stampSlide.FindShapesByMediaId(captionItemsList, _mediaShape.CaptionMediaId).ToDictionary(shapeItem => shapeItem.InternalId);
            List<Effect> effects = _stampSlide.GetMediaBookMarkEffects(_mediaShape);
            foreach (var indexedCaption in captions.Select((captionItem, index) => new { CaptionItem = captionItem, Index = index }))
            {
                CaptionItem currentCaption = indexedCaption.CaptionItem;
                int currentIndex = indexedCaption.Index;

                StampCaptionShape captionShape = currentCaption.IsNew 
                                                     ? CreateCaptionTextBox(currentCaption) 
                                                     : shapesByInternalId[currentCaption.InternalId];
                if (currentCaption.IsDirty && !currentCaption.IsNew)
                {
                    if (currentCaption.IsDirtyCaptionText)
                    {
                        captionShape.Text = currentCaption.Text;
                        captionShape.Name = currentCaption.Name;
                    }
                    if (currentCaption.IsDirtyCaptionStyle || currentCaption.IsDirtyCaptionText)
                    {
                        UpdateShapeStyle(currentCaption, captionShape);
                    }
                }
                captionShape.UpdateTransparency(_mediaShape.Transparency);
                if ((currentCaption.IsDirtyStartTime || currentCaption.IsDirtyEndTime || currentCaption.IsNew))
                {
                    DeleteBookMarksAndAnimations(captions, currentCaption, captionShape, currentIndex, effects);
                    captionShape.UpdateTags(_mediaShape.CaptionMediaId, currentCaption);
                    captionShape.Name = currentCaption.Name;
                    _mediaShape.AddBookMarks(currentCaption);
                    captionShape.AddAnimations(_stampSlide.AddSequenceForAnimations(), _mediaShape);
                }
                captionShape.UpdateTags(_mediaShape.CaptionMediaId, currentCaption);
                observer.Update(currentIndex);
            }
            foreach (Effect effect in effects)
            {
                effect.Timing.Duration = _mediaShape.FadeDuration;
            }
            _mediaShape.ShowCaptions();
            Utils.CleanTempShape();
        }
        
        /// <summary>
        /// Creates the captions shapes based on the caption details.
        /// </summary>
        /// <param name="caption">Caption item.</param>
        private StampCaptionShape CreateCaptionTextBox(CaptionItem caption)
        {
            CaptionStyle captionStyle = caption.CaptionStyle;
            LayoutManager layoutManager = new LayoutManager(_stampSlide, _mediaShape, captionStyle);
            captionStyle.CaptionLocation = layoutManager.DetermineCaptionLocation();
            float fontSize = LoadFontSizeIfFontIsNull(caption.CaptionStyle).ToPointSize();
            RectangleF position = layoutManager.DetermineCaptionTextBoxPosition(caption.Text, fontSize);
            StampCaptionShape textbox = _stampSlide.AddTextBox(position, caption);
            textbox.UpdateTags(_mediaShape.CaptionMediaId, caption);
            bool isCaptionsHidden = _mediaShape.IsCaptionsHidden;
            textbox.ApplyStyle(caption.CaptionStyle, fontSize, isCaptionsHidden);
            textbox.UpdateSize(position);
            
            return textbox;
        }

        private Length LoadFontSizeIfFontIsNull(CaptionStyle captionStyle)
        {
            if (captionStyle.FontSize != null) 
                return captionStyle.FontSize;
            float textAreaWidth;
            if (_mediaShape.IsMovie && captionStyle.CaptionLocation != CaptionLocation.SlideBottom)
                textAreaWidth = _mediaShape.Width;
            else 
                textAreaWidth = _stampSlide.Width;
            return new FontSizeCalculator(textAreaWidth).GetFontSize();
        }

        private void UpdateShapeStyle(CaptionItem caption, StampCaptionShape dirtyShape)
        {
            LayoutManager layoutManager = new LayoutManager(_stampSlide, _mediaShape, caption.CaptionStyle);
            caption.CaptionStyle.CaptionLocation = layoutManager.DetermineCaptionLocation();
            Length fontSize = LoadFontSizeIfFontIsNull(caption.CaptionStyle);
            dirtyShape.ApplyStyle(caption.CaptionStyle, fontSize.Value, _mediaShape.IsCaptionsHidden);
            RectangleF position = layoutManager.DetermineCaptionTextBoxPosition(caption.Text, fontSize.ToPointSize());
            dirtyShape.UpdateSize(position);
        }

        private void DeleteBookMarksAndAnimations(IList<CaptionItem> captions, CaptionItem caption, StampCaptionShape captionShape, int currentCaptionIndex, List<Effect> existingEffects)
        {

            string exitBookmarkName = captionShape.BookMarkExitName;
            Effect[] effects = existingEffects.Where(effectItem => EffectShouldBeDeleted(effectItem, exitBookmarkName, captionShape, caption)).ToArray();
            int distinctShapeNamesCount = effects.Select(effectItem => effectItem.Shape.Name).Distinct().Count();
            bool bookmarkHasOtherAnimations = distinctShapeNamesCount > (caption.IsNew ? 0 : 1);
            for (int i = effects.Length - 1; i >= 0; i--)
            {
                Effect currentEffect = effects[i];
                existingEffects.Remove(currentEffect);
                currentEffect.Delete();
            }
            DeleteBookMarkIfSafe(captionShape.BookMarkEntryName);
            _mediaShape.DeleteBookmarkByName(exitBookmarkName);
            if (captions.Count > currentCaptionIndex + 1)
                captions[currentCaptionIndex + 1].IsDirtyStartTime = bookmarkHasOtherAnimations;
        }

        private void DeleteBookMarkIfSafe(string bookmarkName)
        {
            if (!_stampSlide.HasEffectForBookmark(bookmarkName, _mediaShape))
                _mediaShape.DeleteBookmarkByName(bookmarkName);
        }

        private static bool EffectShouldBeDeleted(Effect effect, string exitBookmarkName, StampCaptionShape captionShape, CaptionItem caption)
        {
            return effect.Timing.TriggerBookmark.Equals(exitBookmarkName)
                   || effect.Shape.Name.Equals(captionShape.Name)
                   || effect.Timing.TriggerBookmark.Equals(caption.BookMarkExitName);
        }

        /// <summary>
        /// Aligns the captions.
        /// </summary>
        /// <param name="captionItemsList">The caption items list.</param>
        public void AlignCaptions(CaptionItemsList captionItemsList)
        {
            IList<StampCaptionShape> captionShapes = _stampSlide.FindShapesByMediaId(captionItemsList, _mediaShape.CaptionMediaId).ToList();
            foreach (StampCaptionShape shape in captionShapes)
            {
                string captionId = shape.InternalId;
                CaptionItem caption = captionItemsList.CaptionItems.First(captionItem => captionItem.InternalId == captionId);
                CaptionStyle captionStyle = caption.CaptionStyle ?? (caption.CaptionStyle = new CaptionStyle());
                LayoutManager layoutManager = new LayoutManager(_stampSlide, _mediaShape, captionStyle);
                captionStyle.CaptionLocation = layoutManager.DetermineCaptionLocation();
                float pointSize = LoadFontSizeIfFontIsNull(captionStyle).ToPointSize();
                RectangleF position = layoutManager.DetermineCaptionTextBoxPosition(caption.Text, pointSize);
                shape.ApplyStyle(captionStyle, pointSize, false);
                shape.UpdateSize(position);
            }
            Utils.CleanTempShape();
        }
    }
}